{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Day07 掌握`?`的用法"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "　　通过前面的日程我们已经对**正则表达式**几乎全部的**元字符**都进行了介绍，但`?`还依然没有介绍，因为`?`在**正则表达式**中不同的使用方式下有着两种功能，而今天我就来带大家学习**正则表达式**中`?`的用法。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "　　在**【Day06】**也就是昨天的日程中，有小伙伴遇到了类似的困惑，下面我举一个简单的例子进行演示："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2020-09-01T10:57:25.049667Z",
     "iopub.status.busy": "2020-09-01T10:57:25.049667Z",
     "iopub.status.idle": "2020-09-01T10:57:25.054655Z",
     "shell.execute_reply": "2020-09-01T10:57:25.053658Z",
     "shell.execute_reply.started": "2020-09-01T10:57:25.049667Z"
    }
   },
   "outputs": [],
   "source": [
    "s  = '[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "　　对于下面的例子，假如我们想要匹配出所有**成对的中括号所包裹的内容以及括号本身**，当我们想当然的书写下列正则时："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2020-09-01T10:57:25.055652Z",
     "iopub.status.busy": "2020-09-01T10:57:25.055652Z",
     "iopub.status.idle": "2020-09-01T10:57:25.065624Z",
     "shell.execute_reply": "2020-09-01T10:57:25.065624Z",
     "shell.execute_reply.started": "2020-09-01T10:57:25.055652Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]']"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import re\n",
    "\n",
    "re.findall('\\[.*\\]', s)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "　　会发现匹配到的结果与设想的相差甚远，把出现的第一个`[`到最后出现的`]`之间的全部内容提取了出来，这与**正则表达式**的默认设定有关：**正则表达式**中设定的`*`、`+`等范围修饰符默认都是贪婪的，即**“优先输出满足条件的结果中最长的那个”**，这就导致了上面例子中类似问题的出现，这时候我们就要使用到`?`的主要功能——修饰之前的子模式，将其从贪婪模式到非贪婪模式："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2020-09-01T10:57:25.067621Z",
     "iopub.status.busy": "2020-09-01T10:57:25.067621Z",
     "iopub.status.idle": "2020-09-01T10:57:25.073605Z",
     "shell.execute_reply": "2020-09-01T10:57:25.072607Z",
     "shell.execute_reply.started": "2020-09-01T10:57:25.067621Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['[1, 2, 3]', '[4, 5, 6]', '[7, 8, 9]', '[10, 11, 12]']"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 非贪婪匹配\n",
    "re.findall('\\[.*?\\]', s)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "　　`?`在网页爬虫书写提取内容的**正则表达式**时非常常用，比如下面的例子，我们要提取所成对标签中夹着的内容，贪婪模式与非贪婪模式之间的差别如下："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2020-09-01T10:57:25.075599Z",
     "iopub.status.busy": "2020-09-01T10:57:25.074600Z",
     "iopub.status.idle": "2020-09-01T10:57:25.084575Z",
     "shell.execute_reply": "2020-09-01T10:57:25.083577Z",
     "shell.execute_reply.started": "2020-09-01T10:57:25.074600Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "贪婪模式： ['<div><a>测试<br></a></div>']\n",
      "非贪婪模式 ['<div>', '测试', '</a>']\n"
     ]
    }
   ],
   "source": [
    "s = '<div><div><a>测试<br></a></div></div>'\n",
    "\n",
    "print('贪婪模式：', re.findall('>(.+)<', s))\n",
    "print('非贪婪模式', re.findall('>(.+?)<', s))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "　　可以看到这时每一小段满足正则要求的内容都被匹配了出来，要想真正获取非标签部分的内容就很容易了："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2020-09-01T10:57:25.085573Z",
     "iopub.status.busy": "2020-09-01T10:57:25.085573Z",
     "iopub.status.idle": "2020-09-01T10:57:25.092554Z",
     "shell.execute_reply": "2020-09-01T10:57:25.091556Z",
     "shell.execute_reply.started": "2020-09-01T10:57:25.085573Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['测试']"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "[_ for _ in re.findall('>(.+?)<', s) if '<' not in _]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "　　而`?`的另一个功能就比较不常用，大家遇到了适合它的情况可以考虑使用，它也可以和`*`、`+`一样充当范围修饰的作用，其意义是**匹配之前模式对应的内容0次或1次**，也有**非贪婪**的思想在其中，譬如我们来匹配下面文本中的所有**1个大写字母+1个数字**或**1个大写字母**的模式，即后面紧跟着需不需要是数字无所谓："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2020-09-01T10:57:25.094547Z",
     "iopub.status.busy": "2020-09-01T10:57:25.094547Z",
     "iopub.status.idle": "2020-09-01T10:57:25.102528Z",
     "shell.execute_reply": "2020-09-01T10:57:25.101531Z",
     "shell.execute_reply.started": "2020-09-01T10:57:25.094547Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['A1', 'B', 'C2', 'D', 'E3']"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "s = 'A1 B C2 D E33'\n",
    "\n",
    "re.findall('[A-Z][0-9]?', s)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Day07 课后小测验\n",
    "\n",
    "　　相信通过上面的内容，你已经对`?`的用法有了一定了解，下面是豆瓣电影上关于《新阴阳魔界2》的某条影评，请你观察规律，提取出每一集的名称，譬如`SE1 心灵感应 颅内连麦网恋 女主诱导男主杀自己丈夫获得解脱`对应的集名为`心灵感应`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2020-09-01T10:57:25.104523Z",
     "iopub.status.busy": "2020-09-01T10:57:25.103525Z",
     "iopub.status.idle": "2020-09-01T10:57:25.108512Z",
     "shell.execute_reply": "2020-09-01T10:57:25.107518Z",
     "shell.execute_reply.started": "2020-09-01T10:57:25.103525Z"
    }
   },
   "outputs": [],
   "source": [
    "target = '''SE1 心灵感应 颅内连麦网恋 女主诱导男主杀自己丈夫获得解脱\n",
    "SE2 虚拟世界 当你努力一辈子终于获得你想要的成功 却突然发你所在的世界不是真实的你会怎么办\n",
    "SE3 灵魂转移 郁郁不得志的男主怀疑女朋友因为自己的落魄要离开自己 怀疑她劈腿 去抢银行结果发现自己的灵魂可以自由穿梭于别人的皮囊 最终他失去了自己的躯壳 在女朋友劈腿对象的皮囊中存在\n",
    "SE4 魔力硬币 带给持有人获得无尽掌声的buff 最后确实一场姐妹相伴的大戏 人心真的不要太贪婪 以及理智追星\n",
    "SE5 校园故事 拥有超能力但被自己超能力禁锢内心孤独的女孩 召唤朋友最后还是BE的故事\n",
    "SE8 小镇模型 上帝之手 这个能力可以有'''"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['心灵感应', '虚拟世界', '灵魂转移', '魔力硬币', '校园故事', '小镇模型']"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "re.findall('[A-Z0-9]+\\s(.+?)\\s',target)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2020-08-26T12:55:19.898241Z",
     "iopub.status.busy": "2020-08-26T12:55:19.897245Z",
     "iopub.status.idle": "2020-08-26T12:55:19.904225Z",
     "shell.execute_reply": "2020-08-26T12:55:19.903227Z",
     "shell.execute_reply.started": "2020-08-26T12:55:19.898241Z"
    }
   },
   "source": [
    "　　请将你的答案截图发到本帖评论区~"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.7"
  },
  "widgets": {
   "application/vnd.jupyter.widget-state+json": {
    "state": {},
    "version_major": 2,
    "version_minor": 0
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
